package com.mspbots.usermanage.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mspbots.api.client.CoreClient;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * com.mspbots.usermanage.security.AuthService
 *
 * @author Jason
 * @date 2019/12/11
 **/
@Log4j2
@Service
public class AuthService {
    public final static String DEFAULT_ADMIN_ROLE_NAME = "ADMIN";
    public final static String DEFAULT_TENANT_ADMIN_ROLE_NAME = "TENANT_ADMIN";
    public final static String DEFAULT_TENANT_USER_ROLE_NAME = "TENANT_USER";
    @Autowired
    protected ObjectMapper objectMapper;
    @Autowired
    protected CoreClient coreClient;
//    @Autowired
//    private Pbkdf2PasswordEncoder passwordEncoder;

/*

    public Mono<Map<String, Object>> oauth(String username) {
        return coreClient.get("/0/account", Map.of("username", username)).flatMap(user -> {
            return coreClient.post("/" + user.get("tenantId").asLong() + "/0/account", Map.of("lastLoginTime", LocalDateTime.now()));
        }).flatMap(user -> {
            Map<String, Object> map = MapUtil.newHashMap(4);
            map.put("authorities", user.getAuthorities());
            map.put("username", user.getUsername());
            map.put("user", user);
            return this.loadTenant(user.getTenantId())
                    .flatMap(tenant -> this.loadTenantUser(tenant.getId(), user.getUsername())
                            .map(u -> {
                                map.put("identifier", u.getIdentifier());
                                map.put("userId", u.getId());
                                log.info("identifier {},userId{}",u.getIdentifier(),u.getId());
                                return map;
                            }).switchIfEmpty(Mono.just(map))
                            .map(m -> {
                                map.put("tenant", tenant);
                                return map;
                            }))
                    .switchIfEmpty(Mono.just(map));
        });
    }

    public Mono<Object> teamsLogin(String teamsUserId, ServerWebExchange exchange) {
        return this.coreClient.get("/teams/user/by-user-id/" + teamsUserId, null, TeamsUser.class)
                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotFoundException("This login teams user by id ["
                        + teamsUserId + "] not found."))))
                .flatMap(teamsUser ->
                        this.loadUser(teamsUser.getTeamsMail())
                                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotFoundException(400, "This login teams user by email ["
                                        + teamsUser.getTeamsMail() + "] have been is null."))))
                                .filter(User::getEnabled)
                                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotPermissionException(505, "This login teams user by id ["
                                        + teamsUserId + "] have been banned by the administrator."))))
                                .flatMap(us->this.loadTenantUser(us.getTenantId(), us.getUsername())
                                        .defaultIfEmpty(new TenantUser())
                                        .map(tu->{
                                            us.setIdentifier(tu.getIdentifier());
                                            return us;
                                        }))
                ).flatMap(u ->
                        this.loadTenant(u.getTenantId())
                                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotFoundException(400, "This login user tenant by id ["
                                        + u.getTenantId() + "] have been is null."))))
                                .flatMap(tenant -> {
                                    UserDetails user = org.springframework.security.core.userdetails.User.withUsername(u.getUsername())
                                            .password(u.getPassword())
                                            .authorities(u.getAuthorities().toArray(new String[0]))
                                            .accountExpired(!u.getEnabled())
                                            .credentialsExpired(!u.getEnabled())
                                            .disabled(!u.getEnabled())
                                            .accountLocked(!u.getEnabled())
                                            .build();
                                    SecurityContext context = new SecurityContextImpl(new UsernamePasswordAuthenticationToken(user,
                                            user.getPassword(), user.getAuthorities()));
                                    return exchange.getSession()
                                            .doOnNext(session ->
                                                    session.getAttributes().put("SPRING_SECURITY_CONTEXT", context))
                                            .map(session -> Map.of("token", session.getId(),
                                                    "tenant", tenant, "user", u, "teamsId", teamsUserId,
                                                    "authorities", u.getAuthorities()));
                                })
                );
    }

    public Mono<User> loadUser(String username) {
        return this.usersService.loadUser(username);
    }

    public Mono<Tenant> loadTenant(long tenantId) {
        return this.coreClient.get("/tenant/" + tenantId + "/info", null, Tenant.class);
    }

    public Mono<TenantUser> loadTenantUser(long tenantId, String email) {
        return this.coreClient.get("/sb/user/info", Map.of("tenantId", tenantId, "email", email), TenantUser.class);
    }

    public Mono<User> register(UserParam request) {
        User user = User.builder().username(request.getUsername().toLowerCase())
                .password(this.passwordEncoder.encode(request.getPassword()))
                .roles(request.getRoles())
                .enabled(true)
                .tenantId(request.getTenantId())
                .build();
        return this.usersService.save(user);

    }

    public Mono<User> changePassword(ChangePasswordDTO changePasswordDTO) {
        return ReactiveSecurityContextHolder.getContext()
                .map(SecurityContext::getAuthentication)
                .filter(Objects::nonNull)
                .switchIfEmpty(Mono.error(new AccessDeniedException("Can't change password as no Authentication object found in context for current user.")))
                .map(Principal::getName)
                .flatMap(username -> this.loadUser(username)
                        .filter(user -> this.passwordEncoder.matches(requestParams.getPassword(), user.getPassword()))
                        .switchIfEmpty(Mono.error(new PasswordIncorrectException("This request old password is error.")))
                        .flatMap(user -> {
                            String newPassword = this.passwordEncoder.encode(requestParams.getNewPassword());
                            user.setPassword(newPassword);
                            user.setLastModified(LocalDateTime.now());
                            return this.usersService.save(user);
                        }).doOnSuccess(user -> {
                            UserDetails userDetails = org.springframework.security.core.userdetails.User.withUsername(user.getUsername())
                                    .password(user.getPassword())
                                    .authorities(user.getAuthorities().toArray(new String[0]))
                                    .accountExpired(!user.getEnabled())
                                    .credentialsExpired(!user.getEnabled())
                                    .disabled(!user.getEnabled())
                                    .accountLocked(!user.getEnabled())
                                    .build();
                            UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(
                                    userDetails, null, userDetails.getAuthorities());
                            newAuthentication.setDetails(user);
                            SecurityContextHolder.getContext().setAuthentication(newAuthentication);
                        })
                );

    }


    public Mono<User> changeUser(UserParam request) {

        return this.usersService.loadUser(request.getUsername())
                .flatMap(user -> {
                    if (!StringUtils.isEmpty(request.getEnable())) {
                        user.setEnabled(request.getEnable());
                    }
                    if (!StringUtils.isEmpty(request.getEmail())) {
                        user.setEmail(request.getEmail());
                    }
                    if (!StringUtils.isEmpty(request.getFirstName())) {
                        user.setFirstName(request.getFirstName());
                    }
                    if (!StringUtils.isEmpty(request.getLastName())) {
                        user.setLastNme(request.getLastName());
                    }
                    if (!StringUtils.isEmpty(request.getPassword())) {
                        user.setPassword(this.passwordEncoder.encode(request.getPassword()));
                    }
                    if (!StringUtils.isEmpty(request.getRoles()) && request.getRoles().size() > 0) {
                        user.setAuthorities(request.getRoles());
                    }
                    return this.usersService.save(user);
                });
    }

    static class PasswordIncorrectException extends RestServerException {

        PasswordIncorrectException(String message) {
            this(500, message);
        }

        PasswordIncorrectException(int code, String msg) {
            super(code, msg);
        }
    }

    static class TeamsUserNotPermissionException extends RestServerException {

        TeamsUserNotPermissionException(String message) {
            this(504, message);
        }

        TeamsUserNotPermissionException(int code, String msg) {
            super(code, msg);
        }
    }

    static class TeamsUserNotFoundException extends RestServerException {

        TeamsUserNotFoundException(String message) {
            this(403, message);
        }

        TeamsUserNotFoundException(int code, String msg) {
            super(code, msg);
        }
    }*/

}
